第04章 逻辑回归模型¶

4.1 逻辑回归模型的算法原理¶

4.1.1 逻辑回归的数学原理¶

回归二字源于其算法原理同样涉及线性回归模型中的线性回归方程

$$ y = k_0 + k_1x_1 + k_2x_2 + \cdots $$

上式用于预测连续变量, 取值范围为$(-\infty, + \infty)$, 而预测分类的概率的取值范围为 $(0,1)$, 所以使用Sigmoid函数将取值范围转化为 $(0,1)$

$$ f(y) = \frac{1}{1 + e^{-y}} $$

Sigmoid

本质就是将线性回归模型进行非线性转换, 得到一个介于 $0 \sim 1$ 之间的概率值, 对于二分类问题, 其预测分类为1的概率为

$$ P = \frac{1}{1 + e^{-(k_0 + k_1x_1 + k_2x_2 + \cdots) }} $$

如果预测分类为1的概率大于0.5, 样本的预测输出就为1

4.1.2 逻辑回归模型的代码实现¶

In [1]:
import warnings

warnings.filterwarnings('ignore')
In [2]:
# 构造数据
X = [[1, 0], [5, 1], [6, 4], [4, 2], [3, 2]]
y = [0, 1, 1, 0, 0]

# 模型训练
from sklearn.linear_model import LogisticRegression # type: ignore
model = LogisticRegression()
model.fit(X, y)  # 如果运行时下面出现FutureWarning警告,不要在意,它只是在告诉你以后模型的官方默认参数会有所调整而已,不是报错
Out[2]:
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
In [3]:
# 模型预测 - 预测单个数据
print(model.predict([[2,2]]))
[0]
In [4]:
# 模型预测 - 预测多个数据1
print(model.predict([[1,1], [2,2], [5, 5]]))

# 模型预测 - 预测多个数据2
print(model.predict([[1, 0], [5, 1], [6, 4], [4, 2], [3, 2]]))  # 因为这里演示的多个数据和X是一样的,所以也可以直接写成model.predict(X)
[0 0 1]
[0 1 1 0 0]

4.1.3 逻辑回归模型的深入理解¶

逻辑回归模型的本质是预测概率, 而不是直接预测具体的类别

In [5]:
# 预测概率:左列是分类为0的概率,右列是分类为1的概率
y_pred_proba = model.predict_proba(X)
y_pred_proba  # 直接打印
Out[5]:
array([[0.97345945, 0.02654055],
       [0.39075704, 0.60924296],
       [0.1798892 , 0.8201108 ],
       [0.63170618, 0.36829382],
       [0.82426878, 0.17573122]])
In [6]:
# 另外一种打印概率的方式:通过DataFrame展示,更加好看些
import pandas as pd # type: ignore
a = pd.DataFrame(y_pred_proba, columns=['分类为0的概率', '分类为1的概率'])  # 2.2.1 通过numpy数组创建DataFrame
a
Out[6]:
分类为0的概率 分类为1的概率
0 0.973459 0.026541
1 0.390757 0.609243
2 0.179889 0.820111
3 0.631706 0.368294
4 0.824269 0.175731
In [7]:
# 打印系数和截距项
print(model.coef_)  # 系数k1与k2
print(model.intercept_)  # 截距项k0

model.coef_.T
[[1.00599764 0.02232318]]
[-4.60818025]
Out[7]:
array([[1.00599764],
       [0.02232318]])

补充知识点: 用逻辑回归模型处理多分类问题¶

In [8]:
# 构造数据,此时y有多个分类
X = [[1, 0], [5, 1], [6, 4], [4, 2], [3, 2]]
y = [-1, 0, 1, 1, 1]  # 这里有三个分类-1、0、1

# 模型训练
from sklearn.linear_model import LogisticRegression # type: ignore
model = LogisticRegression()
model.fit(X, y)  # 如果运行时下面出现FutureWarning警告,不要在意,它只是在告诉你以后模型的官方默认参数会有所调整而已,不是报错
Out[8]:
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
In [9]:
print(model.predict([[0, 0]]))
[-1]
In [10]:
print(model.predict_proba([[0, 0]]))
[[0.88354196 0.02339895 0.09305909]]

4.2 案例实战: 客户流失预警模型¶

In [11]:
# 1.读取数据
import pandas as pd # type: ignore
df = pd.read_excel('股票客户流失.xlsx')
df.head()
Out[11]:
账户资金(元) 最后一次交易距今时间(天) 上月交易佣金(元) 累计交易佣金(元) 本券商使用时长(年) 是否流失
0 22686.5 297 149.25 2029.85 0 0
1 190055.0 42 284.75 3889.50 2 0
2 29733.5 233 269.25 2108.15 0 1
3 185667.5 44 211.50 3840.75 3 0
4 33648.5 213 353.50 2151.65 0 1
In [12]:
# 2.划分特征变量和目标变量
X = df.drop(columns='是否流失') 
y = df['是否流失']   
In [13]:
# 3.划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)  # 设置random_state使得每次划分的数据一样

X_train.head()  
Out[13]:
账户资金(元) 最后一次交易距今时间(天) 上月交易佣金(元) 累计交易佣金(元) 本券商使用时长(年)
1814 43251.5 192 98.50 2258.35 0
5946 304449.5 22 369.50 5160.55 3
3881 441357.5 9 325.75 6681.75 5
2389 587076.5 2 427.25 8300.85 5
3676 204027.5 39 352.00 4044.75 2
In [14]:
# 4.模型搭建
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X_train, y_train)
Out[14]:
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
In [15]:
# 5.模型使用1 - 预测数据结果
y_pred = model.predict(X_test)
print(y_pred[0:100])  # 打印预测内容的前100个看看
[0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 0 0
 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 1]
In [16]:
# 放到一个DataFrame里进行查看比对
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
a.head()  # 可以看到此时前5个预测准确度为80%
Out[16]:
预测值 实际值
0 0 0
1 0 0
2 0 0
3 0 1
4 0 0
In [17]:
# 查看全部的预测准确度
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)

# 另外一种查看模型预测准确度的方法
model.score(X_test, y_test)
0.7977288857345636
Out[17]:
0.7977288857345636
In [18]:
# 6.模型使用2 - 预测概率
y_pred_proba = model.predict_proba(X_test)  
y_pred_proba[0:5]
Out[18]:
array([[0.82041509, 0.17958491],
       [0.84029656, 0.15970344],
       [0.7981937 , 0.2018063 ],
       [0.62989223, 0.37010777],
       [0.61636604, 0.38363396]])
In [19]:
# 另一种查看概率的方式
a = pd.DataFrame(y_pred_proba, columns=['不流失概率', '流失概率'])
a.head()
Out[19]:
不流失概率 流失概率
0 0.820415 0.179585
1 0.840297 0.159703
2 0.798194 0.201806
3 0.629892 0.370108
4 0.616366 0.383634
In [20]:
# 7.查看各个特征变量的系数
model.coef_
Out[20]:
array([[ 2.41952552e-05,  8.16880529e-03,  1.04321146e-02,
        -2.54894683e-03, -1.10120675e-04]])

4.3 模型评估方法: ROC曲线与KS曲线¶

二分类模型的主流评估方法有ROC曲线和KS曲线两种

ROC曲线

  • 命中率与假报警率(横轴)的曲线

    ROC曲线

  • 命中率与假报警率

    |命中率(真正率)|True Positive Rate (TPR)|TPR = TP/(TP+FN)| |--|--|--| |假报警率(假正率)|False Positive Rate (FPR)| FPR = FP/(FP + TN)|

  • TP FP TN FN 的含义

    ||1(预测流失)|0(预测不流失)|合计| |--|--|--|--| |1(实际流失)|True Positive(TP)正确肯定|False Negative (FN)漏报|TP + FN| |0(实际不流失)|False Positive(FP)虚报|True Negative(TN)正确否定|FP + TN|

如果把假报警率理解为代价, 那么命中率就是收益, 所以可以说在阈值相同的情况下, 希望假报警率尽可能小, 命中率尽可能高, 反映在图形上就是ROC曲线尽可能地陡峭

  • 其他指标

    |名称|公式|含义| |---|---|---| |precision(精确率)|$TP/(TP+FP)$|预测为流失中实际流失的比例| |f1-socre|$2TP/(2TP + FP + FN)$|混合的度量,对不平衡类别比较有效|

  • AUC值

    • 衡量模型好坏

    • ROC曲线下方的面积

混淆矩阵的Python代码实现¶

In [21]:
from sklearn.metrics import confusion_matrix
m = confusion_matrix(y_test, y_pred)  # 传入预测值和真实值
print(m)
[[968  93]
 [192 156]]
In [22]:
a = pd.DataFrame(m, index=['0(实际不流失)', '1(实际流失)'], columns=['0(预测不流失)', '1(预测流失)'])
a
Out[22]:
0(预测不流失) 1(预测流失)
0(实际不流失) 968 93
1(实际流失) 192 156
In [23]:
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))  # 传入预测值和真实值
              precision    recall  f1-score   support

           0       0.83      0.91      0.87      1061
           1       0.63      0.45      0.52       348

    accuracy                           0.80      1409
   macro avg       0.73      0.68      0.70      1409
weighted avg       0.78      0.80      0.79      1409

4.3.2 案例实战: 用ROC曲线评估客户流失预警模型¶

In [24]:
y_pred_proba[:,1]
Out[24]:
array([0.17958491, 0.15970344, 0.2018063 , ..., 0.04220526, 0.09782415,
       0.63586812])
In [25]:
# 1.计算ROC曲线需要的假警报率(fpr)、命中率(tpr)及阈值(thres)
from sklearn.metrics import roc_curve
fpr, tpr, thres = roc_curve(y_test, y_pred_proba[:,1])
In [26]:
# 2.查看假警报率(fpr)、命中率(tpr)及阈值(thres)
a = pd.DataFrame()  # 创建一个空DataFrame 
a['阈值'] = list(thres)
a['假警报率'] = list(fpr)
a['命中率'] = list(tpr)
a.head()
Out[26]:
阈值 假警报率 命中率
0 inf 0.000000 0.000000
1 0.930369 0.000000 0.002874
2 0.867342 0.000000 0.034483
3 0.864187 0.001885 0.034483
4 0.857304 0.001885 0.040230
In [27]:
# 3.绘制ROC曲线
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文
plt.plot(fpr, tpr)  # 通过plot()函数绘制折线图
plt.title('ROC曲线')  # 添加标题,注意如果要写中文,需要在之前添加一行代码:plt.rcParams['font.sans-serif'] = ['SimHei']
plt.xlabel('FPR')  # 添加X轴标签
plt.ylabel('TPR')  # 添加Y轴标
plt.show()
No description has been provided for this image
In [28]:
# 4.求出模型的AUC值
from sklearn.metrics import roc_auc_score
score = roc_auc_score(y_test, y_pred_proba[:,1])
score
Out[28]:
np.float64(0.8103854528908967)

该面积的取值范围通常为0.5~1, 0.5表示随机判断, 1则代表完美的模型, 在商业实战中, AUC值能达到0.75以上就已经可以接受, 能达到0.85就是非常不错的模型

4.3.3 KS曲线的基本原理¶

将命中率与假报警率之差作为纵坐标, 阈值作为横坐标

In [29]:
from sklearn.metrics import roc_curve
fpr, tpr, thres = roc_curve(y_test, y_pred_proba[:,1])
In [30]:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['阈值'] = list(thres)
a['假警报率'] = list(fpr)
a['命中率'] = list(tpr)
a.head()
Out[30]:
阈值 假警报率 命中率
0 inf 0.000000 0.000000
1 0.930369 0.000000 0.002874
2 0.867342 0.000000 0.034483
3 0.864187 0.001885 0.034483
4 0.857304 0.001885 0.040230
In [31]:
plt.plot(thres[1:], tpr[1:])
plt.plot(thres[1:], fpr[1:])
plt.plot(thres[1:], tpr[1:] - fpr[1:])
plt.xlabel('threshold')
plt.legend(['tpr', 'fpr', 'tpr-fpr'])
plt.gca().invert_xaxis() 
plt.show()
No description has been provided for this image
In [32]:
max(tpr - fpr)
Out[32]:
np.float64(0.4744656418256471)
In [33]:
# KS值对应的阈值
a['TPR-FPR'] = a['命中率'] - a['假警报率']
print(a.head())
         阈值      假警报率       命中率   TPR-FPR
0       inf  0.000000  0.000000  0.000000
1  0.930369  0.000000  0.002874  0.002874
2  0.867342  0.000000  0.034483  0.034483
3  0.864187  0.001885  0.034483  0.032598
4  0.857304  0.001885  0.040230  0.038345
In [34]:
# 获取KS值对应的阈值等信息
print(a[a['TPR-FPR'] == max(a['TPR-FPR'])])
          阈值      假警报率       命中率   TPR-FPR
224  0.27769  0.255419  0.729885  0.474466